﻿/**
* project3D Engine
* @author John Sword
* @version 2 - AS3
*/

/* 
* Based on Papervision's Face class
* papervision3d.org • blog.papervision3d.org • osflash.org/papervision3d
* Copyright 2006 (c) Carlos Ulloa Matesanz, noventaynueve.com.
*/

package engine.geom
{
	
	import engine.materials.Material;
	import engine.objects.Object3D;
	
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.geom.Matrix;

	public class Face
	{
		/**
		* An array of Vertex objects for the three vertices of the triangle.
		*/
		//public var vertices :Array;
		public var v1:Vertex;
		public var v2:Vertex;
		public var v3:Vertex;

		/**
		* An array of {x,y} objects for the corresponding UV pixel coordinates of each triangle vertex.
		*/
		//public var uvs:Array;
		public var uv1:UV;
		public var uv2:UV;
		public var uv3:UV;

		/**
		* [read-only] The average depth (z coordinate) of the transformed triangle. 
		* Also known as the distance from the camera. 
		* Used internally for z-sorting.
		*/
		public var Z:Number;

		/**
		* [read-only] A Boolean value that indicates that the face is visible, 
		* i.e. it's vertices are in front of the camera.
		*/
		public var visible:Boolean = false;

		private var material:Material;
		private var matrix:Matrix = new Matrix();

		private var _a:Number;
		private var _b:Number;
		private var _c:Number;
		private var _d:Number;
		private var _tx:Number;
		private var _ty:Number;
		
		private var x0:Number;
		private var y0:Number;
		private var x1:Number;
		private var y1:Number;
		private var x2:Number;
		private var y2:Number;

		/**
		* The Face constructor
		*/
		public function Face ( v1:Vertex,v2:Vertex,v3:Vertex,uv1:UV,uv2:UV,uv3:UV,m:Material )
		{
			// Vertices
			this.v1 = v1;
			this.v2 = v2;
			this.v3 = v3;
			// uvs
			//uvs = uv;
			this.uv1 = uv1;
			this.uv2 = uv2;
			this.uv3 = uv3;
			// Material
			this.material = m;
			// set texture if needed
			if( material.texture ) 
			{
				// set vertices x,y positions
				x0 = v1.screen.x;
				y0 = v1.screen.y;
				x1 = v2.screen.x;
				y1 = v2.screen.y;
				x2 = v3.screen.x;
				y2 = v3.screen.y;
				transformUV ( material.texture );
			}
			
		}

		/**
		* Applies the updated UV texture mapping values to the triangle. This is required to speed up rendering.
		*
		*/
		public function transformUV ( texture:BitmapData ) : void
		{
			try {
				if( texture )
				{
					var w  :Number = texture.width;
					var h  :Number = texture.height;
					
					var uu0 :Number = uv1.u * w;
					var vv0 :Number = uv1.v * h;
					var uu1 :Number = uv2.u * w;
					var vv1 :Number = uv2.v * h;
					var uu2 :Number = uv3.u * w;
					var vv2 :Number = uv3.v * h;

					// Fix perpendicular projections
					if( (uu0 == uu1 && vv0 == vv1) || (uu0 == uu2 && vv0 == vv2) )
					{
						uu0 -= (uu0 > 0.05)? 0.05 : -0.05;
						vv0 -= (vv0 > 0.07)? 0.07 : -0.07;
					}

					if( uu2 == uu1 && vv2 == vv1 )
					{
						uu2 -= (uu2 > 0.05)? 0.04 : -0.04;
						vv2 -= (vv2 > 0.06)? 0.06 : -0.06;
					}

					// Precalculate matrix
					var at :Number = ( uu1 - uu0 );
					var bt :Number = ( vv1 - vv0 );
					var ct :Number = ( uu2 - uu0 );
					var dt :Number = ( vv2 - vv0 );

					var m :Matrix = new Matrix( at, bt, ct, dt, uu0, vv0 );
					m.invert();

					_a  = m.a;
					_b  = m.b;
					_c  = m.c;
					_d  = m.d;
					_tx = m.tx;
					_ty = m.ty;
				}
			} catch (e:Error) {}
			
		}

		/**
		* draws faces
		*
		* @param	screen		default screen
		* @return				an object under the mouse pointer
		*
		*/
		public function render ( screen:Sprite ) : Object3D
		{

			material.render ( this, screen );
			
			if ( v1.o )
			{
				// detect if mouse is inside poly
				if ( intersects( screen.mouseX, screen.mouseY ) )
				{
					return v1.o;
				}
			}
			
			return null;
		}
		
		private function intersects ( xpos:Number, ypos:Number ) : Boolean
		{
			
			//trace( xpos )
			//trace( ypos )
			//trace("");
			//if ( !xpos && !ypos ) return false;
			
			// The ray's begining point
			var origin:Vector 		= new Vector(xpos,ypos,0);
			// The direction of the ray into infinity (or a very large number)
			var direction:Vector 	= new Vector(xpos,ypos,1000000);
			// compute distances' vectors
			var diff:Vector 		= origin.subtract(v1.screen);
			var edge1:Vector 		= v2.screen.subtract(v1.screen);
			var edge2:Vector 		= v3.screen.subtract(v1.screen);
			var norm:Vector 		= edge1.cross ( edge2 );

			var dirDotNorm:Number = direction.dot(norm);
			
			var sign:Number;
			if (dirDotNorm > 1) {
			    sign = 1;
			} else if (dirDotNorm < -1) {
			    sign = -1;
			    dirDotNorm = -dirDotNorm;
			} else {
			    // ray and triangle are parallel
			    return false;
			}
	        
	        var dirDotDiffxEdge2:Number = sign * direction.dot(diff.cross(edge2));
			if (dirDotDiffxEdge2 > 0) {
				var dirDotEdge1xDiff:Number = sign * direction.dot(edge1.crossLocal(diff));
				if (dirDotEdge1xDiff >= 0) {
				    if ( dirDotDiffxEdge2 + dirDotEdge1xDiff <= dirDotNorm ) {
				        var diffDotNorm:Number = -sign * diff.dot(norm);
				        if (diffDotNorm >= 0) {				        	
				            // ray intersects triangle
				            var inv:Number = 1 / dirDotNorm;
				            var t:Number = diffDotNorm * inv;
				            var o:Object3D = v1.o;
				            o.pickFace = this;
			                // these weights are used to determine
			                // interpolated values, such as the texture coord.
			                // eg. texcoord s,t at intersection point:
			                // s = w0*s0 + w1*s1 + w2*s2;
			                // t = w0*t0 + w1*t1 + w2*t2;
			                var w1:Number = dirDotDiffxEdge2 * inv;
			                var w2:Number = dirDotEdge1xDiff * inv;
			                var w0:Number = 1 - w1 - w2;
			                // interpolate texture coordinates at point
			                o.pickUV.u = w0*uv1.u + w1*uv2.u + w2*uv3.u;
							o.pickUV.v = w0*uv1.v + w1*uv2.v + w2*uv3.v;
							//trace( "U: "+(u)+" V: "+(v));
							// interpolate xyz position
			                o.pickPos.x = w0*v1.x + w1*v2.x + w2*v3.x;
				            o.pickPos.y = w0*v1.y + w1*v2.y + w2*v3.y;
				            o.pickPos.z = w0*v1.z + w1*v2.z + w2*v3.z;
				            //trace( o.pickPos )
				            return true;
				        }
				    }
				}
			}
			return false;
		}
		
		public function getTextureMatrix () : Matrix
		{

			var a1:Number 	= _a,
			b1	:Number 	= _b,
			c1 	:Number 	= _c,
			d1 	:Number 	= _d,
			tx1 :Number 	= _tx,
			ty1 :Number		= _ty,
			a2 	:Number 	= x1 - x0,
			b2 	:Number 	= y1 - y0,
			c2 	:Number 	= x2 - x0,
			d2 	:Number 	= y2 - y0;

			matrix.a 	= a1*a2 + b1*c2;
			matrix.b 	= a1*b2 + b1*d2;
			matrix.c 	= c1*a2 + d1*c2;
			matrix.d 	= c1*b2 + d1*d2;
			matrix.tx 	= tx1*a2 + ty1*c2 + x0;
			matrix.ty 	= tx1*b2 + ty1*d2 + y0;			

			return matrix;			
		}
		
		public function getMaterial () : Material
		{
			return material;
		}
		
		public function getMatrix () : Matrix
		{
			return matrix;
		}
		
		public function setVisible ( screen:Sprite, zc:int, fp:Boolean, c:Boolean ) : Boolean
		{
			
			visible = false;
			
			// check if face vertices are all visible
			//if ((v1.visible + v2.visible + v3.visible) >0)
			//{
			if (v1.visible && v2.visible && v3.visible)
			{	
				
				// get screen size
				var top:int = screen.y,
				left:int = screen.x,
				w:int = screen.width,
				h:int = screen.height;
		
				// get vertices x,y positions
				x0 = v1.screen.x;
				y0 = v1.screen.y;
				x1 = v2.screen.x;
				y1 = v2.screen.y;
				x2 = v3.screen.x;
				y2 = v3.screen.y;

				// check if face is inside screen boundaries	
				if ( x0 < left && x1 < left && x2 < left ) return false;
				if ( x0 > w && x1 > w  && x2 > w ) return false;
				if ( y0 < top && y1 < top  && y2 < top ) return false;
				if ( y0 > h && y1 > h && y2 > h ) return false;
				
				// calculate average z position
				Z = int(( v1.screen.z + v2.screen.z + v3.screen.z ) / 3);
				
				if ( !c ) // check if object has culling faces
				{
					visible = true;
				} else {

					if ( fp ) // object has normals flipped
					{
						if ( ((x1 - x0)*(y2 - y0)-(y1 - y0)*(x2 - x0)) > 0 )
						{
							if (Z > zc)	visible = true;
						}
					} else {
						if ( ((x1 - x0)*(y2 - y0)-(y1 - y0)*(x2 - x0)) < 0 )
						{
							if (Z > zc)	visible = true;
						}
					}

				}
				
			}
			return visible;
		}
		
		/*
		private function PntInTriangle (Px:Number, Py:Number, x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, y3:Number) : Boolean
		{
			var Or1:int  = Orientation(x1, y1, x2, y2, Px, Py);
			var Or2:int  = Orientation(x2, y2, x3, y3, Px, Py);
			var Or3:int  = Orientation(x3, y3, x1, y1, Px, Py);
			return (Or1 == Or2) && (Or2 == Or3);
		}

		private function Orientation (x1:Number, y1:Number, x2:Number, y2:Number, Px:Number, Py:Number) : int
		{
			var Orin:Number = (x2 - x1) * (Py - y1) - (Px - x1) * (y2 - y1);
			if (Orin > 0) return 1;	// Orientation is to the right-hand side
			else if (Orin < 0) return -1; // Orientation is to the left-hand side
			return 0;	// Orientation is neutral if result is 0
		}
		*/
	}
	
}

